/*
 * Copyright [2011] [Pettersen Consulting]
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *  
 *   http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.   
 */
package com.edb.os.xstream.writer;

import java.io.IOException;
import java.io.Writer;

import org.apache.commons.lang.WordUtils;

import com.edb.os.xstream.ClassWriterFactory;

/**
 * Main abstract class providing common functionality to all outputters
 * 
 * @author Oystein Pettersen
 */
public abstract class ClassWriter {

    protected final String name;
    protected final Class<?> clazz;
    protected String value;
    private final int depthIndex;
    protected final ClassWriterFactory classWriterFactory;

    public ClassWriter(String name, Class<?> clazz, int depthIndex, ClassWriterFactory classWriterFactory) {
        this.depthIndex = depthIndex;
        this.classWriterFactory = classWriterFactory;
        this.name = createName(name);
        this.clazz = clazz;
    }

    public void create(Writer writer) {
        // assume that the class has a constructor. Find the default one an new it.
        if (hasEmptyConstructor()) {
            try {
                writer.write(String.format("%s %s = new %s();\n", clazz.getName(), paramName(), clazz.getName()));
            } catch (IOException e) {
                handleException(e);
            }
        }
    }

    public void writeReturnStatement(Writer writer) {
        try {
            writer.write(String.format("return %s;\n", paramName()));
        } catch (IOException e) {
            handleException(e);
        }
    }

    protected boolean hasEmptyConstructor() {
        try {
            clazz.getConstructor();
            return true;
        } catch (Exception e) {
            System.err.println(String.format("Provided class: %s does not have a default constructor, need to provide a special writer for this", clazz.getName()));
            handleException(e);
        }

        return false;
    }

    protected String paramName() {
        return name;
    }

    public Class<?> theClass() {
        return clazz;
    }

    public String name() {
        return name;
    }

    protected void handleException(Exception e) {
        e.printStackTrace();
        // throw new IllegalStateException(e);
    }

    private String createName(String name) {
        String result = name;
        if (name.contains(".")) {
            String substring = name.substring(name.lastIndexOf(".") + 1);
            result = WordUtils.uncapitalize(substring);
        }

        return result;
    }

    public void setValue(String text) {
        this.value = text;
    }

    public void add(ClassWriter childAttribute, Writer writer) {
        
        // do nothing for empty writer.
        if (childAttribute instanceof EmptyWriter) {
            return;
        }
        
        // assume that this is a normal setter for the child attribute
        String methodName = deduceSetMethod(childAttribute);
        try {
            this.clazz.getMethod(methodName, childAttribute.clazz);
            writer.write(String.format("%s.%s(%s);\n", paramName(), methodName, childAttribute.getValue()));
        } catch (Exception e) {
            System.err.println("unable to set element for: " + childAttribute.name);
        }
    }

    private String deduceSetMethod(ClassWriter childAttribute) {
        
        // check if this the child is boolean if so check for set/is convension
        if(boolean.class.isAssignableFrom(childAttribute.theClass()) || Boolean.class.isAssignableFrom(childAttribute.theClass())) {
            
            if(childAttribute.name().startsWith("is")) {
                String attributeCore = childAttribute.name().substring(2);
                String methodName = String.format("set%s", attributeCore);
                try {
                    this.clazz.getMethod(methodName, childAttribute.clazz);
                    return methodName;
                } catch (Exception e) {
                    // do nothing just let the default set method kick in
                    System.err.println("Unable to find convensional boolean set method. Using default set");
                } 
            }
        }
        
        return String.format("set%s", WordUtils.capitalize(childAttribute.name));
    }

    public int depthIndex() {
        return depthIndex;
    }

    public String getValue() {
        // by default just return the parameter name. Assume that this is a complex class
        return paramName();
    }

    public ClassWriter createChildWriter(String childName, int childDepthIndex) {
        return new EmptyWriter(childName, Void.class, childDepthIndex, classWriterFactory);
    }

    public ClassWriter createChildWriter(String childName, Class<?> childClass, int childDepth, int childParamNameIndex) {
        return classWriterFactory.create(childName, childClass, childDepth, childParamNameIndex);
    }
}
